package com.i72.freeway;

import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.beans.BeanCopier;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author jiangj
 * @version 1.0.0
 * @ClassName ObjectHelper.java
 * @Description TODO
 * @createTime 2021年12月28日 15:57:00
 */
@Slf4j
public class ObjectHelper {

    private static Map<String, BeanCopier> beanCopierMap = new HashMap<>();

    /**
     * 把对象的属性转成Map
     * @param object 待转换的对象
     * @param filterEnum 过滤配置枚举
     * @return {@code Map<String,Object>}
     * @author 刘海峰
     * @since 2017/11/28-9:14
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *      public void toMap() throws Exception {
     *          FlowDaoModel flowDaoModel= new FlowDaoModel();
     *          flowDaoModel.setAmount(3.0);
     *          flowDaoModel.setFlowid("1");
     *          Map map= ObjectHelper.toMap(flowDaoModel);
     *          Assert.assertNotNull(map);
     *          Assert.assertEquals(4,map.size());
     *          Assert.assertEquals("1",map.get("flowid"));
     *          Assert.assertEquals(3.0,map.get("amount"));
     *      }
     * }
     * </pre>
     *
     * </blockquote>
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> toMap(Object object, FilterEnum filterEnum) {
        if (object == null) {
            return null;
        }
        if (object instanceof Map) {
            return filterMap((Map) object, filterEnum);
        }
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            Map<String, Object> result = new HashMap<>(propertyDescriptors.length);
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                String key = propertyDescriptor.getName();
                if (!key.equals("class")) {
                    Method getter = propertyDescriptor.getReadMethod();
                    if (getter != null) {
                        Object value = getter.invoke(object);
                        if (FilterEnum.FILTER_NULL == filterEnum && value == null) {
                            continue;
                        } else if (FilterEnum.FILTER_EMPTY == filterEnum && StringHelper.isObjectNullOrEmpty(value)) {
                            continue;
                        }
                        result.put(key, value);
                    }
                }
            }
            return result;
        } catch (Exception ex) {
            throw new RuntimeException("ObjectHelper.toMap failure", ex);
        }
    }

    public static Map<String, Object> toMap(Object object) {
        return toMap(object, FilterEnum.NONE);
    }



    /**
     * 反射机制把对象的属性转成Map
     * @param value 待转换的对象
     * @return {@code Map<String,Object>}
     * @author 刘海峰
     * @since 2017/11/28-9:14
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *      public void reflectToMap() throws Exception {
     *          FlowDaoModel flowDaoModel= new FlowDaoModel();
     *          flowDaoModel.setAmount(3.0);
     *          flowDaoModel.setFlowid("1");
     *          Map map= ObjectHelper.reflectToMap(flowDaoModel);
     *          Assert.assertNotNull(map);
     *          Assert.assertEquals(4,map.size());
     *          Assert.assertEquals("1",map.get("flowid"));
     *          Assert.assertEquals(3.0,map.get("amount"));
     *      }
     * }
     * </pre>
     *
     * </blockquote>
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> reflectToMap(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Map) {
            return (Map) value;
        }
        try {
            Class<?> clazz = value.getClass();
            Map<String, Object> result = new HashMap<>();
            while (clazz != null && Object.class != clazz) {
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    field.setAccessible(true);
                    result.put(field.getName(), field.get(value));
                }
                clazz = clazz.getSuperclass();
            }
            return result;
        } catch (Exception ex) {
            throw new RuntimeException("ObjectHelper.reflectToMap failure", ex);
        }
    }



    /**
     * 校验时间格式:内部调用
     * @param date 待校验的时间
     * @return 时间格式化格式
     * @author 曾利娟
     * @since 2018/05/29
     */

    public static String getDateType(String date) {
        String type = "yyyy-MM-dd HH:mm:ss";
        int result = date.trim().indexOf(":");
        if (result < 0) {
            type = "yyyy-MM-dd";
        }
        return type;
    }

    /**
     * 转换对象为指定类型
     * @param type 目标类型
     * @param source 待转换的对象
     * @author 刘海峰
     * @since 2017/11/28-9:14
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *      public void convertToType() throws Exception {
     *          Float r1=Float.parseFloat("10");
     *          result = ObjectHelper.convertToType(Float.class,"10");
     *          Assert.assertEquals(Float.class,result.getClass());
     *          Assert.assertEquals(r1,result);
     *      }
     * }
     * </pre>
     *
     * </blockquote>
     **/
    public static Object convertToType(Class<?> type, Object source) throws ParseException {
        if (!StringHelper.isObjectNullOrEmpty(source)) {
            String typeName = type.getName();
            String temp = source.toString().trim();
            if (!temp.equals("")) {
                switch (typeName) {
                    case "java.lang.String":
                        return source.toString();
                    case "java.util.Date":
                        if (source.getClass().getName().equals("java.sql.Timestamp")) {
                            return new Date(((Timestamp) source).getTime());
                        } else {
                            String dateType = getDateType(temp);
                            return new SimpleDateFormat(dateType).parse(temp);
                        }
                    case "java.sql.Date":
                        if (source.getClass().getName().equals("java.sql.Timestamp")) {
                            return new java.sql.Date(((Timestamp) source).getTime());
                        } else {
                            return java.sql.Date.valueOf(temp);
                        }
                    case "java.sql.Timestamp":
                        if (source.getClass().getName().equals("java.sql.Timestamp")) {
                            return source;
                        } else {
                            return Timestamp.valueOf(temp);
                        }
                    case "byte":
                    case "java.lang.Byte":
                        return Byte.parseByte(temp);
                    case "short":
                    case "java.lang.Short":
                        return Short.parseShort(temp);
                    case "int":
                    case "java.lang.Integer":
                        return Integer.parseInt(temp);
                    case "long":
                    case "java.lang.Long":
                        return Long.parseLong(temp);
                    case "double":
                    case "java.lang.Double":
                        return Double.parseDouble(temp);
                    case "float":
                    case "java.lang.Float":
                        return Float.parseFloat(temp);
                    case "boolean":
                    case "java.lang.Boolean":
                        return Boolean.parseBoolean(temp);
                    case "java.sql.Clob":
                        return source;
                    case "java.math.BigDecimal":
                        if (source instanceof BigDecimal) {
                            return source;
                        } else {
                            return new BigDecimal(temp);
                        }
                }
            }
        }
        return source;
    }

    /**
     * 复制实体类
     * @param src 源实体
     * @param dst 目标实体类型T
     * @return 复制后的实体T
     * @author 洪锦城
     * @since 2018/7/12
     * <p>
     * <strong>示例一：源实体和目标实体是相同类型的</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *      public void copy() {
     *          FlowDaoModel flowDaoModel= new FlowDaoModel();
     *          flowDaoModel.setAmount(3.0);
     *          flowDaoModel.setFlowid("1");
     *          FlowDaoModel resultVo = ObjectHelper.copy(flowDaoModel,FlowDaoModel.class);
     *          Assert.assertNotNull(resultVo);
     *          Assert.assertEquals(flowDaoModel.getAmount(),resultVo.getAmount());
     *          Assert.assertEquals(flowDaoModel.getFlowid(),resultVo.getFlowid());
     *          Assert.assertNull(resultVo.getFlowdetailid());
     *          Assert.assertNull(resultVo.getFlowtypeid());
     *     }
     * }
     * </pre>
     *
     * </blockquote>
     * <p>
     * <strong>示例二：源实体和目标实体是不同类型的</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *      public void copy2() {
     *          MysqlSelectModel entity1 = new MysqlSelectModel();
     *          entity1.setId("id1");//字段名相同但是字段类型不同
     *          entity1.setCreateDate(new Date());//字段名和类型都相同
     *          entity1.setTestBit(true);//目标实体中没有这个字段
     *
     *          MysqlSelect2Model resultVo = ObjectHelper.copy(entity1,MysqlSelect2Model.class);
     *          Assert.assertNotNull(resultVo);
     *          Assert.assertNull(resultVo.getId());
     *          Assert.assertEquals(entity1.getCreateDate(),resultVo.getCreateDate());
     *          Assert.assertNull(resultVo.getSex());
     *     }
     * }
     * </pre>
     *
     * </blockquote>
     */
    public static <T> T copy(Object src, Class<T> dst) {
        if (src == null || dst == null) return null;

        T dstInstance = null;
        try {
            dstInstance = dst.newInstance();
            copy(src, dstInstance);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("copy from " + src.getClass().getName() + " to " + dst.getName() + " failure", e);
        }

        return dstInstance;
    }

    /**
     * 复制实体类
     * @param src 源实体
     * @param dst 目标实体
     * @author 洪锦城
     * @since 2018/7/12
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *  public void  copy(){
     *     FlowDaoModel flowDaoModel= new FlowDaoModel();
     *     flowDaoModel.setAmount(3.0);
     *     flowDaoModel.setFlowid("1");
     *
     *     FlowDaoModel resultVo = new FlowDaoModel();
     *     resultVo.setFlowdetailid("源实体没有设置这个属性");
     *
     *     ObjectHelper.copy(flowDaoModel,resultVo);//无返回值
     *
     *     Assert.assertNotNull(resultVo);
     *     Assert.assertEquals(flowDaoModel.getAmount(),resultVo.getAmount());
     *     Assert.assertEquals(flowDaoModel.getFlowid(),resultVo.getFlowid());
     *     Assert.assertNull(resultVo.getFlowdetailid());
     *     Assert.assertNull(resultVo.getFlowtypeid());
     * }
     * }
     * </pre>
     *
     * </blockquote>
     */
    public static <T> void copy(Object src, T dst) {
        if (src == null || dst == null) return;

        String key = src.getClass().getName() + dst.getClass().getName();
        BeanCopier copier;

        if (beanCopierMap.containsKey(key)) {
            copier = beanCopierMap.get(key);
        } else {
            synchronized (ObjectHelper.class) {
                if (beanCopierMap.containsKey(key)) {
                    copier = beanCopierMap.get(key);
                } else {
                    copier = BeanCopier.create(src.getClass(), dst.getClass(), false);
                    beanCopierMap.put(key, copier);
                }
            }
        }

        copier.copy(src, dst, null);
    }

    /**
     * 获取参数
     * @param arg 参数
     * @return 返回结果是map
     * @author 刘海峰
     * @since 2018/3/31
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     * public void getParameter(){
     *      Map<String, Object> map = ObjectHelper.getParameter("name","zlj","monther","12");
     *      Assert.assertEquals(2,map.size());
     *      Assert.assertEquals("zlj",map.get("name"));
     *      Assert.assertEquals("12",map.get("monther"));
     * }
     * }
     * </pre>
     *
     * </blockquote>
     */

    public static Map<String, Object> getParameter(Object... arg) {
        if (arg == null || arg.length == 0 || arg.length % 2 != 0) {
            log.error("argument not valid");
            return null;
        }
        Map<String, Object> param = new HashMap<>();
        for (int i = 0; i < arg.length; i += 2) {
            param.put(arg[i].toString(), arg[i + 1]);
        }
        return param;
    }

    /**
     * 增加参数
     * @param param 增加目标map
     * @param arg 带增加参数
     * @return 新增参数后的map
     * @author 刘海峰
     * @since 2018/3/31
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *  public void addParameter(){
     *       Map<String, Object> oldMap=new HashMap<>();
     *       ObjectHelper.addParameter(oldMap,"key1","value1");
     *       oldMap.put("name","rose");
     *       oldMap.put("age","18");
     *
     *       Map<String, Object> map = ObjectHelper.addParameter(oldMap,"sex",1);
     *       Assert.assertEquals(3,map.size());
     *       Assert.assertEquals("rose",map.get("name"));
     *       Assert.assertEquals("18",map.get("age"));
     *       Assert.assertEquals(1,map.get("sex"));
     *  }
     * }
     * </pre>
     *
     * </blockquote>
     **/

    public static Map<String, Object> addParameter(Map<String, Object> param, Object... arg) {
        if (param == null || arg == null || arg.length == 0 || arg.length % 2 != 0) {
            log.error("argument not valid");
            return param;
        }
        for (int i = 0; i < arg.length; i += 2) {
            param.put(arg[i].toString(), arg[i + 1]);
        }
        return param;
    }

    /**
     * @param prototype 被拷贝的对象
     * @return 拷贝对象
     * @author 余焕【yuh@3vjia.com】
     * @since 2018/7/4 15:27
     * <p>
     * <strong>示例：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     * public void deepClone(){
     *      UserTest userTest= new UserTest();
     *      userTest.setOs("a");
     *      userTest.setBxId("b");
     *      userTest.setName("c");
     *      userTest.setSs("d");
     *      UserTest result = new UserTest();
     *      result.setTs("test");
     *      result = (UserTest)CloneHelper.deepClone(userTest);
     *
     * }
     * }
     * </pre>
     *
     * </blockquote>
     */
    public static Object deepClone(Object prototype) {
        ByteArrayOutputStream byteOut = null;
        ByteArrayInputStream byteIn = null;
        ObjectOutputStream objOut = null;
        ObjectInputStream objIn = null;
        try {
            byteOut = new ByteArrayOutputStream();
            objOut = new ObjectOutputStream(byteOut);
            objOut.writeObject(prototype);
            byteIn = new ByteArrayInputStream(byteOut.toByteArray());
            objIn = new ObjectInputStream(byteIn);
            return objIn.readObject();
        } catch (IOException e) {
            throw new RuntimeException("Clone Object failed in IO.", e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class not found.", e);
        } finally {
            try {
                if (objIn != null) objIn.close();
            } catch (IOException ignored) {}
            try {
                if (byteIn != null) byteIn.close();
            } catch (IOException ignored) {}
            try {
                if (objOut != null) objOut.close();
            } catch (IOException ignored) {}
            try {
                if (byteOut != null) byteOut.close();
            } catch (IOException ignored) {}
        }
    }

    /**
     * 判断一个类是JAVA类型还是用户定义类型
     * @param clazz class
     * @return true: JAVA类型 false: 用户定义类型
     */
    public static boolean isJavaClass(Class<?> clazz) {
        return clazz != null && clazz.getClassLoader() == null;
    }

    /**
     * Map转成keyValues集合形式
     */
    public static <T> List<T> mapToKeyValues(Map<T, T> map) {
        List<T> result = new ArrayList<>();
        if(map==null || map.keySet().size()<=0){
        //if (MapUtils.isEmpty(map)) {
            return result;
        }
        for (Map.Entry<T, T> entry : map.entrySet()) {
            result.add(entry.getKey());
            result.add(entry.getValue());
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    public static Map filterMap(Map map, FilterEnum filterEnum) {
        if (FilterEnum.NONE == filterEnum) {
            return map;
        }
        Predicate<Map.Entry<Object, Object>> predicate;
        if (FilterEnum.FILTER_NULL == filterEnum) {
            predicate = entry -> entry.getValue() != null;
        } else {
            predicate = entry -> !StringHelper.isObjectNullOrEmpty(entry.getValue());
        }
        return ((Map<Object, Object>) map).entrySet().stream().filter(predicate).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    public enum FilterEnum {
        /** 过滤null值 */
        FILTER_NULL,
        /** 过滤null和空""值 */
        FILTER_EMPTY,
        /** 不做过滤处理 */
        NONE
    }

}
